package com.amkai.aes.webservice.binary.objecthandler.billing;

import java.math.BigDecimal;
import java.util.ArrayList;
import java.util.List;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.hibernate.Query;

import com.amkai.aes.aescommon.billing.AutoPostingTableRow;
import com.amkai.aes.aescommon.billing.BillingConstants;
import com.amkai.aes.aescommon.exceptions.AOException;
import com.amkai.aes.aescommon.ledger.managers.BaseAmountManager;
import com.amkai.aes.aescommon.ledger.managers.ChargeManager;
import com.amkai.aes.aescommon.ledger.managers.TransferManager;
import com.amkai.aes.aescommon.ledger.managers.details.FromToManager;
import com.amkai.aes.aescommon.ledger.utils.BaseAmountUtils;
import com.amkai.aes.aescommon.ledger.utils.ChargeUtils;
import com.amkai.aes.aescommon.service.binary.job.billing.AutoPostingJob;
import com.amkai.aes.aescommon.service.result.billing.AutoPostingResult;
import com.amkai.aes.aescommon.utilities.PatientUtils;
import com.amkai.aes.generatedbeans.billinggroup.BillingGroup;
import com.amkai.aes.generatedbeans.billinggroup.BillingGroupInsurance;
import com.amkai.aes.generatedbeans.billingledger.Charge;
import com.amkai.aes.generatedbeans.billingledger.ChargeDebit;
import com.amkai.aes.generatedbeans.billingledger.Entry;
import com.amkai.aes.generatedbeans.billingledger.Payment;
import com.amkai.aes.generatedbeans.billingledger.Transfer;
import com.amkai.aes.generatedbeans.billingledger.WriteOff;
import com.amkai.aes.generatedbeans.organization.Department;
import com.amkai.aes.generatedbeans.passport.Passport;
import com.amkai.aes.generatedbeans.registration.Patient;
import com.amkai.aes.server.billing.ledger.LedgerManager;
import com.amkai.aes.server.billing.ledger.ServerLedgerEntryLoadManager;
import com.amkai.aes.server.session.ServiceSessionManager;
import com.amkai.aes.webservice.binary.JobObjectHandler;
import com.amkai.common.extensions.hibernate.config.SiteManager;
import com.amkai.common.extensions.hibernate.config.manager.SessionManager;

public class AutoPostingHandler implements JobObjectHandler<AutoPostingJob, Department, AutoPostingResult> {

	private static final String TITLE = "Auto Posting Server";
	private static final Log log = LogFactory.getLog(AutoPostingHandler.class);
	
	@Override
	public AutoPostingResult process(AutoPostingJob job, Department dep) throws Exception {
		switch (job.getKind()) {
		case INIT:	return initAutoPosting(job, dep);
		case SEARCH:	return search(job, dep);
		case APPLY:	return applyAutoPosting(job, dep);
		}
		throw new AOException(TITLE, job.getClass().getName(), String.format("Unknown JobKind: %s", job.getKind()));
	}
	
	protected AutoPostingResult initAutoPosting(AutoPostingJob job, Department moduleDepartment) throws Exception {
		AutoPostingResult result = new AutoPostingResult();
		SessionManager sm = SiteManager.Start();
		try {
			Patient patient = sm.get(Patient.class, job.getPatientID());
			List<BillingGroup> billingGroups = new ArrayList<BillingGroup>();
			billingGroups.addAll(PatientUtils.getBillingGroups(patient, moduleDepartment));
			for (BillingGroup bg : billingGroups) {
				bg.getPmCases().size();
				if (bg.getPrimaryGuarantor() != null)
					bg.getPrimaryGuarantor().getDisplayName();
				if (bg.getSecondaryGuarantor() != null)
					bg.getSecondaryGuarantor().getDisplayName();
				for (BillingGroupInsurance bgi : bg.getBillingGroupInsurances()) {
					bgi.getInsurancePolicy().getInsuranceClaimOffice().getInsuranceCarrier().getName();
					if (bgi.getInsurancePolicy().getInsurancePlan().getDefaultPaymentJournalCode() != null)
						bgi.getInsurancePolicy().getInsurancePlan().getDefaultPaymentJournalCode().getName();
					if (bgi.getInsurancePolicy().getInsurancePlan().getDefaultWriteoffJournalCode() != null)
						bgi.getInsurancePolicy().getInsurancePlan().getDefaultWriteoffJournalCode().getName();
					if (bgi.getInsurancePolicy().getInsurancePlan().getDefaultWriteoff2JournalCode() != null)
						bgi.getInsurancePolicy().getInsurancePlan().getDefaultWriteoff2JournalCode().getName();
					if (bgi.getInsurancePolicy().getInsurancePlan().getDefaultDebitJournalCode() != null)
						bgi.getInsurancePolicy().getInsurancePlan().getDefaultDebitJournalCode().getName();
				}
			}
			
			result.setBillingGroups(billingGroups);
		}
		catch (Exception ex) {
			log.error(TITLE, ex);
			result.setException(ex);
		}
		finally {
			sm.closeSession();
		}
		return result;
	}
	
	protected AutoPostingResult search(AutoPostingJob job, Department moduleDepartment) throws Exception {
		AutoPostingResult result = new AutoPostingResult();
		SessionManager sm = SiteManager.Start();
		try {
			
			boolean useCaseFiltering = true;
			if (job.getFrom() != null || job.getThru() != null)
				useCaseFiltering = false;
			
			StringBuilder sb = new StringBuilder("FROM Charge c " +
				"WHERE c.isActive = true " +
				"AND c.billingGroup.id=:billing_group_id ");
	
			if (useCaseFiltering) {
				sb.append("AND c.pmCase.id=:case_id ");
			}
			else {
				if (job.getFrom() != null)
					sb.append("AND c.transactionDate >= :from_date ");
				if (job.getThru() != null)
					sb.append("AND c.transactionDate <= :thru_date ");
			}

			log.debug(sb.toString());
			
			Query query = sm.execute(sb.toString())
				.setParameter("billing_group_id", job.getBillingGroupID());
			
			if (useCaseFiltering)
				query.setParameter("case_id", job.getCaseID());
			else {
				if (job.getFrom() != null)
					query.setParameter("from_date", job.getFrom());
				if (job.getThru() != null)
					query.setParameter("thru_date", job.getThru());
			}

			List<Charge> l = query.list();
			
			List<AutoPostingTableRow> searchResult = new ArrayList<AutoPostingTableRow>();
			for (Charge charge : l) {
				if (FromToManager.equals(charge.getFromEntry(), job.getSourceOfPayment())) {
					if (!job.isOpenChargesOnly() || BigDecimal.ZERO.compareTo(charge.getDue()) < 0) {
						searchResult.add(new AutoPostingTableRow(charge));
					}
				}
			}
			result.setSearchResult(searchResult);
		}
		catch (Exception ex) {
			log.error(TITLE, ex);
			result.setException(ex);
		}
		finally {
			sm.closeSession();
		}
		return result;
	}
	
	protected AutoPostingResult applyAutoPosting(AutoPostingJob job, Department moduleDepartment) throws Exception {
		AutoPostingResult result = new AutoPostingResult();
		SessionManager sm = SiteManager.Start();
		try {
			sm.openTransaction();
			
			List<Entry> charges = new ArrayList<Entry>(); 
			for (AutoPostingTableRow row : job.getRows()) {
				Charge charge = sm.get(Charge.class, row.getChargeID());
				ServerLedgerEntryLoadManager.getChildEntries(charge);
				Passport owner = ServiceSessionManager.getConnection().getRuntimePassport();
				if (BaseAmountUtils.isAmountValid(row.getPaymentAmount())) {
					Payment payment = ChargeUtils.createPayment(charge, owner);
					
					// NOT job.getAutoPostingUI().getPaymentAmount() !!!!
					payment.setAmount(row.getPaymentAmount());
					
					payment.setJournalCode(job.getAutoPostingUI().getPaymentJournalCode());
					payment.setBatch(job.getBatch());
					payment.setTransactionDate(job.getAutoPostingUI().getTransactionDate());
					payment.setPaymentType(job.getAutoPostingUI().getPaymentType());
					payment.setCheckNumber(job.getAutoPostingUI().getCheckNumber());
					payment.setFreePaymentText(job.getAutoPostingUI().getFreePaymentType());
					payment.setFromEntry(FromToManager.clone(charge.getFromEntry()));
					payment.putTranzientProperty(BillingConstants.INSERT_SEQUENCE_KEY, 1);
				}
				
				if (BaseAmountUtils.isAmountValid(row.getWriteOff1Amount())) {
					WriteOff writeOff = ChargeUtils.createWriteOff(charge, owner);
					
					// NOT job.getAutoPostingUI().getWriteOff1Amount !!!!
					writeOff.setAmount(row.getWriteOff1Amount());
					
					writeOff.setJournalCode(job.getAutoPostingUI().getWriteOff1JournalCode());
					writeOff.setBatch(job.getBatch());
					writeOff.setTransactionDate(job.getAutoPostingUI().getTransactionDate());
					writeOff.setDenialCodePair(job.getAutoPostingUI().getWriteOff1DenialCodePair());
					writeOff.putTranzientProperty(BillingConstants.INSERT_SEQUENCE_KEY, 2);
					writeOff.setFromEntry(FromToManager.clone(charge.getFromEntry()));
				}
				
				if (BaseAmountUtils.isAmountValid(row.getWriteOff2Amount())) {
					WriteOff writeOff = ChargeUtils.createWriteOff(charge, owner);
					
					// NOT job.getAutoPostingUI().getWriteOff2Amount !!!!
					writeOff.setAmount(row.getWriteOff2Amount());
					
					writeOff.setJournalCode(job.getAutoPostingUI().getWriteOff2JournalCode());
					writeOff.setBatch(job.getBatch());
					writeOff.setTransactionDate(job.getAutoPostingUI().getTransactionDate());
					writeOff.setDenialCodePair(job.getAutoPostingUI().getWriteOff2DenialCodePair());
					writeOff.putTranzientProperty(BillingConstants.INSERT_SEQUENCE_KEY, 3);
					writeOff.setFromEntry(FromToManager.clone(charge.getFromEntry()));
				}

				if (BaseAmountUtils.isAmountValid(row.getDebitAmount())) {
					ChargeDebit debit = ChargeUtils.createDebit(charge, owner);
					
					// NOT job.getAutoPostingUI().getDebitAmount !!!!
					debit.setAmount(row.getDebitAmount());
					
					debit.setBatch(job.getBatch());
					debit.setDenialCodePair(job.getAutoPostingUI().getDebitDenialCodePair());
					debit.setFromEntry(FromToManager.clone(charge.getFromEntry()));
					debit.setJournalCode(job.getAutoPostingUI().getDebitJournalCode());
					debit.setTransactionDate(job.getAutoPostingUI().getTransactionDate());
					debit.putTranzientProperty(BillingConstants.INSERT_SEQUENCE_KEY, 4);
				}
				
				BigDecimal due = BaseAmountManager.calculateDue(charge).abs();
				if (row.isTransfer() && BigDecimal.ZERO.compareTo(due) < 0 && job.getAutoPostingUI().getTransferTo().getKind() != charge.getFromEntry().getKind()) {
					Transfer transfer = ChargeUtils.createTransfer(charge, owner);
					transfer.setBatch(job.getBatch());
					transfer.setFromEntry(FromToManager.clone(charge.getFromEntry()));
					transfer.setToEntry(job.getAutoPostingUI().getTransferTo());
					transfer.setJournalCode(job.getAutoPostingUI().getTransferJournalCode());
					transfer.setTransactionDate(job.getAutoPostingUI().getTransactionDate());
					transfer.setAmount(due.abs());
					charge.setLastTransferDate(transfer.getTransactionDate());
					
					//FIXME : use static methods
					ChargeManager cm = new ChargeManager<Charge>(charge);
					cm.setFromByTransfer(new TransferManager<Transfer>(transfer));
					transfer.putTranzientProperty(BillingConstants.INSERT_SEQUENCE_KEY, 5);
					
					if (!ChargeManager.isPatientRelated(charge)) {
						charge.setGenerateClaim(job.getAutoPostingUI().isGenerateClaim());
					}
				}

				charge.getChargeAdditionalInformation().setDocumentNeeded(job.getAutoPostingUI().isDocumentNeeded());
				charge.getChargeAdditionalInformation().setExcludeFromEMC(job.getAutoPostingUI().isExcludeFromEMC());
				
				charges.add(charge);
			}
			LedgerManager.saveEntries(sm, charges);
			
			sm.commitTransaction();
		}
		catch (Exception ex) {
			sm.rollbackTransaction();
			log.error(TITLE, ex);
			result.setException(ex);
		}
		finally {
			sm.closeSession();
		}
		return result;
	}	
}
